const _ = require('underscore');
const AVRequest = require('./request')._request;
const { getSessionToken } = require('./utils');

module.exports = function(AV) {
  const getUser = (options = {}) => {
    const sessionToken = getSessionToken(options);
    if (sessionToken) {
      return AV.User._fetchUserBySessionToken(getSessionToken(options));
    }
    return AV.User.currentAsync();
  };

  const getUserPointer = options =>
    getUser(options).then(currUser =>
      AV.Object.createWithoutData('_User', currUser.id)._toPointer()
    );

  /**
   * Contains functions to deal with Status in LeanCloud.
   * @class
   */
  AV.Status = function(imageUrl, message) {
    this.data = {};
    this.inboxType = 'default';
    this.query = null;
    if (imageUrl && typeof imageUrl === 'object') {
      this.data = imageUrl;
    } else {
      if (imageUrl) {
        this.data.image = imageUrl;
      }
      if (message) {
        this.data.message = message;
      }
    }
    return this;
  };

  _.extend(
    AV.Status.prototype,
    /** @lends AV.Status.prototype */ {
      /**
       * Gets the value of an attribute in status data.
       * @param {String} attr The string name of an attribute.
       */
      get: function(attr) {
        return this.data[attr];
      },
      /**
       * Sets a hash of model attributes on the status data.
       * @param {String} key The key to set.
       * @param {} value The value to give it.
       */
      set: function(key, value) {
        this.data[key] = value;
        return this;
      },
      /**
       * Destroy this status,then it will not be avaiable in other user's inboxes.
       * @param {AuthOptions} options
       * @return {Promise} A promise that is fulfilled when the destroy
       *     completes.
       */
      destroy: function(options) {
        if (!this.id)
          return Promise.reject(new Error('The status id is not exists.'));
        var request = AVRequest('statuses', null, this.id, 'DELETE', options);
        return request;
      },
      /**
       * Cast the AV.Status object to an AV.Object pointer.
       * @return {AV.Object} A AV.Object pointer.
       */
      toObject: function() {
        if (!this.id) return null;
        return AV.Object.createWithoutData('_Status', this.id);
      },
      _getDataJSON: function() {
        var json = _.clone(this.data);
        return AV._encode(json);
      },
      /**
       * Send a status by a AV.Query object.
       * @since 0.3.0
       * @param {AuthOptions} options
       * @return {Promise} A promise that is fulfilled when the send
       *     completes.
       * @example
       *     // send a status to male users
       *     var status = new AVStatus('image url', 'a message');
       *     status.query = new AV.Query('_User');
       *     status.query.equalTo('gender', 'male');
       *     status.send().then(function(){
       *              //send status successfully.
       *      }, function(err){
       *             //an error threw.
       *             console.dir(err);
       *      });
       */
      send: function(options = {}) {
        if (!getSessionToken(options) && !AV.User.current()) {
          throw new Error('Please signin an user.');
        }
        if (!this.query) {
          return AV.Status.sendStatusToFollowers(this, options);
        }

        return getUserPointer(options)
          .then(currUser => {
            var query = this.query._getParams();
            query.className = this.query.className;
            var data = {};
            data.query = query;
            this.data = this.data || {};
            this.data.source = this.data.source || currUser;
            data.data = this._getDataJSON();
            data.inboxType = this.inboxType || 'default';

            return AVRequest('statuses', null, null, 'POST', data, options);
          })
          .then(response => {
            this.id = response.objectId;
            this.createdAt = AV._parseDate(response.createdAt);
            return this;
          });
      },

      _finishFetch: function(serverData) {
        this.id = serverData.objectId;
        this.createdAt = AV._parseDate(serverData.createdAt);
        this.updatedAt = AV._parseDate(serverData.updatedAt);
        this.messageId = serverData.messageId;
        delete serverData.messageId;
        delete serverData.objectId;
        delete serverData.createdAt;
        delete serverData.updatedAt;
        this.data = AV._decode(serverData);
      },
    }
  );

  /**
   * Send a status to current signined user's followers.
   * @since 0.3.0
   * @param {AV.Status} status  A status object to be send to followers.
   * @param {AuthOptions} options
   * @return {Promise} A promise that is fulfilled when the send
   *     completes.
   * @example
   *     var status = new AVStatus('image url', 'a message');
   *     AV.Status.sendStatusToFollowers(status).then(function(){
   *              //send status successfully.
   *      }, function(err){
   *             //an error threw.
   *             console.dir(err);
   *      });
   */
  AV.Status.sendStatusToFollowers = function(status, options = {}) {
    if (!getSessionToken(options) && !AV.User.current()) {
      throw new Error('Please signin an user.');
    }
    return getUserPointer(options).then(currUser => {
      var query = {};
      query.className = '_Follower';
      query.keys = 'follower';
      query.where = { user: currUser };
      var data = {};
      data.query = query;
      status.data = status.data || {};
      status.data.source = status.data.source || currUser;
      data.data = status._getDataJSON();
      data.inboxType = status.inboxType || 'default';

      var request = AVRequest('statuses', null, null, 'POST', data, options);
      return request.then(function(response) {
        status.id = response.objectId;
        status.createdAt = AV._parseDate(response.createdAt);
        return status;
      });
    });
  };

  /**
   * <p>Send  a status from current signined user to other user's private status inbox.</p>
   * @since 0.3.0
   * @param {AV.Status} status  A status object to be send to followers.
   * @param {String} target The target user or user's objectId.
   * @param {AuthOptions} options
   * @return {Promise} A promise that is fulfilled when the send
   *     completes.
   * @example
   *     // send a private status to user '52e84e47e4b0f8de283b079b'
   *     var status = new AVStatus('image url', 'a message');
   *     AV.Status.sendPrivateStatus(status, '52e84e47e4b0f8de283b079b').then(function(){
   *              //send status successfully.
   *      }, function(err){
   *             //an error threw.
   *             console.dir(err);
   *      });
   */
  AV.Status.sendPrivateStatus = function(status, target, options = {}) {
    if (!getSessionToken(options) && !AV.User.current()) {
      throw new Error('Please signin an user.');
    }
    if (!target) {
      throw new Error('Invalid target user.');
    }
    var userObjectId = _.isString(target) ? target : target.id;
    if (!userObjectId) {
      throw new Error('Invalid target user.');
    }
    return getUserPointer(options).then(currUser => {
      var query = {};
      query.className = '_User';
      query.where = { objectId: userObjectId };
      var data = {};
      data.query = query;
      status.data = status.data || {};
      status.data.source = status.data.source || currUser;
      data.data = status._getDataJSON();
      data.inboxType = 'private';
      status.inboxType = 'private';

      var request = AVRequest('statuses', null, null, 'POST', data, options);
      return request.then(function(response) {
        status.id = response.objectId;
        status.createdAt = AV._parseDate(response.createdAt);
        return status;
      });
    });
  };

  /**
   * Count unread statuses in someone's inbox.
   * @since 0.3.0
   * @param {AV.User} owner The status owner.
   * @param {String} inboxType The inbox type, 'default' by default.
   * @param {AuthOptions} options
   * @return {Promise} A promise that is fulfilled when the count
   *     completes.
   * @example
   *  AV.Status.countUnreadStatuses(AV.User.current()).then(function(response){
   *    console.log(response.unread); //unread statuses number.
   *    console.log(response.total);  //total statuses number.
   *  });
   */
  AV.Status.countUnreadStatuses = function(
    owner,
    inboxType = 'default',
    options = {}
  ) {
    if (!_.isString(inboxType)) options = inboxType;
    if (!getSessionToken(options) && owner == null && !AV.User.current()) {
      throw new Error('Please signin an user or pass the owner objectId.');
    }
    return Promise.resolve(owner || getUser(options)).then(owner => {
      var params = {};
      params.inboxType = AV._encode(inboxType);
      params.owner = AV._encode(owner);
      return AVRequest(
        'subscribe/statuses/count',
        null,
        null,
        'GET',
        params,
        options
      );
    });
  };

  /**
   * reset unread statuses count in someone's inbox.
   * @since 2.1.0
   * @param {AV.User} owner The status owner.
   * @param {String} inboxType The inbox type, 'default' by default.
   * @param {AuthOptions} options
   * @return {Promise} A promise that is fulfilled when the reset
   *     completes.
   * @example
   *  AV.Status.resetUnreadCount(AV.User.current()).then(function(response){
   *    console.log(response.unread); //unread statuses number.
   *    console.log(response.total);  //total statuses number.
   *  });
   */
  AV.Status.resetUnreadCount = function(
    owner,
    inboxType = 'default',
    options = {}
  ) {
    if (!_.isString(inboxType)) options = inboxType;
    if (!getSessionToken(options) && owner == null && !AV.User.current()) {
      throw new Error('Please signin an user or pass the owner objectId.');
    }
    return Promise.resolve(owner || getUser(options)).then(owner => {
      var params = {};
      params.inboxType = AV._encode(inboxType);
      params.owner = AV._encode(owner);
      return AVRequest(
        'subscribe/statuses/resetUnreadCount',
        null,
        null,
        'POST',
        params,
        options
      );
    });
  };

  /**
   * Create a status query to find someone's published statuses.
   * @since 0.3.0
   * @param {AV.User} source The status source, typically the publisher.
   * @return {AV.Query} The query object for status.
   * @example
   *   //Find current user's published statuses.
   *   var query = AV.Status.statusQuery(AV.User.current());
   *   query.find().then(function(statuses){
   *      //process statuses
   *   });
   */
  AV.Status.statusQuery = function(source) {
    var query = new AV.Query('_Status');
    if (source) {
      query.equalTo('source', source);
    }
    return query;
  };

  /**
   * <p>AV.InboxQuery defines a query that is used to fetch somebody's inbox statuses.</p>
   * @class
   */
  AV.InboxQuery = AV.Query._extend(
    /** @lends AV.InboxQuery.prototype */ {
      _objectClass: AV.Status,
      _sinceId: 0,
      _maxId: 0,
      _inboxType: 'default',
      _owner: null,
      _newObject: function() {
        return new AV.Status();
      },
      _createRequest: function(params, options) {
        return AV.InboxQuery.__super__._createRequest.call(
          this,
          params,
          options,
          '/subscribe/statuses'
        );
      },

      /**
       * Sets the messageId of results to skip before returning any results.
       * This is useful for pagination.
       * Default is zero.
       * @param {Number} n the mesage id.
       * @return {AV.InboxQuery} Returns the query, so you can chain this call.
       */
      sinceId: function(id) {
        this._sinceId = id;
        return this;
      },
      /**
       * Sets the maximal messageId of results。
       * This is useful for pagination.
       * Default is zero that is no limition.
       * @param {Number} n the mesage id.
       * @return {AV.InboxQuery} Returns the query, so you can chain this call.
       */
      maxId: function(id) {
        this._maxId = id;
        return this;
      },
      /**
       * Sets the owner of the querying inbox.
       * @param {AV.User} owner The inbox owner.
       * @return {AV.InboxQuery} Returns the query, so you can chain this call.
       */
      owner: function(owner) {
        this._owner = owner;
        return this;
      },
      /**
       * Sets the querying inbox type.default is 'default'.
       * @param {Object} type The inbox type.
       * @return {AV.InboxQuery} Returns the query, so you can chain this call.
       */
      inboxType: function(type) {
        this._inboxType = type;
        return this;
      },
      _getParams: function() {
        var params = AV.InboxQuery.__super__._getParams.call(this);
        params.owner = AV._encode(this._owner);
        params.inboxType = AV._encode(this._inboxType);
        params.sinceId = AV._encode(this._sinceId);
        params.maxId = AV._encode(this._maxId);
        return params;
      },
    }
  );

  /**
   * Create a inbox status query to find someone's inbox statuses.
   * @since 0.3.0
   * @param {AV.User} owner The inbox's owner
   * @param {String} inboxType The inbox type,'default' by default.
   * @return {AV.InboxQuery} The inbox query object.
   * @see AV.InboxQuery
   * @example
   *   //Find current user's default inbox statuses.
   *   var query = AV.Status.inboxQuery(AV.User.current());
   *   //find the statuses after the last message id
   *   query.sinceId(lastMessageId);
   *   query.find().then(function(statuses){
   *      //process statuses
   *   });
   */
  AV.Status.inboxQuery = function(owner, inboxType) {
    var query = new AV.InboxQuery(AV.Status);
    if (owner) {
      query._owner = owner;
    }
    if (inboxType) {
      query._inboxType = inboxType;
    }
    return query;
  };
};
